iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
自我挑戰組

從C到JS的同步非同步探索系列 第 18

[Day 18] Node.js 的非同步小實驗

  • 分享至 

  • xImage
  •  

前言

有在寫 node 的人可能聽人提過, node 的底層是一個支援非同步 IO 的 threadpool , 而有讀過我前三篇的讀者應該可以聯想到 epoll 和 IOCP 兩種非同步 IO model 的現象, 就是創建一群閒置的 thread , 每當有一個 IO 發生就喚醒一個新的 thread 來運行。如果 threadpool 每個 thread 都在使用中, 就先讓 IO 事件排好隊等著。我們今天就試著讓 node 呈現這種現象。

實驗

實驗步驟

  1. 創建一個 node 後端 API , 其功能為渲染一個 html 頁回傳
  2. 寫一個前端頁面裡面包含大量內容, 其會被步驟 1 的 API 渲染
  3. 再寫一個 node 應用程式, 可以利用非同步方法同時呼叫第一步的 API
  4. 觀察 process 與 thread 的狀態

用 express 寫了一下 API

import express = require('express');
const router = express.Router();

router.get('/', (req: express.Request, res: express.Response) => {
	// Disable caching for content files
	res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
	res.header('Pragma', 'no-cache');
	res.header('Expires', '0');
	res.render('index', { title: 'Express' });
});

export default router;

再寫一個沒有功能但有大量內容的前端頁面

extends layout

block content
  h1= title
  p Welcome to #{title}
  h1= title
  p Welcome to #{title}
  h1= title
  p Welcome to #{title}
  h1= title
  p Welcome to #{title}
  h1= title
  p Welcome to #{title}
  h1= title
  p Welcome to #{title}
  h1= title
  p Welcome to #{title}
  h1= title
  p Welcome to #{title}
  h1= title
// 往下延伸 5000 行

再寫一個 node 應用, 呼叫 API

const fetch = require('node-fetch');

for (let i = 0; i < 50; i++)
	fetch('http://127.0.0.1:1337/', { method: 'GET' }).then((res) => {
		if (res.status == 200) {
			const time = new Date();
			console.log('success : ' + time);
		} else {
			const time = new Date();
			console.log('error : ' + time);
		}
	});

次數設定成同時呼叫 50 次

實驗記錄

  1. 啟動 API server 以及 node 應用, 觀察 process

https://ithelp.ithome.com.tw/upload/images/20210918/201311648UY7mVArkq.png

可以看到這兩個應用程式的 process 正在運行。
  1. 觀察 API server 的 thread 狀態

    API 被呼叫前
    https://ithelp.ithome.com.tw/upload/images/20210918/20131164RomsY6qdW0.png

    API 正在運作時

https://ithelp.ithome.com.tw/upload/images/20210918/20131164zc9W8gBwST.png

可以理解為 thread 31612 是 main thread

其他 4 條是幫助他完成任務的 threadpool

而 threadpool 在這裡做的就是讀取網頁頁面與進行渲染, main thread 做的是進行 network IO
  1. 觀察 call API 的 node 應用

https://ithelp.ithome.com.tw/upload/images/20210918/201311642EPKHOo8Zs.png

可以發現由 main thread 發出 IO 後, 就只有 main Thread 在等待回傳, threadpool 基本都在閒置
  1. 給 API 觸發時間加上標記, 以及回傳時加上閒置時間
router.get(
	'/',
	(req: express.Request, res: express.Response, next: express.NextFunction) => {
		console.log('In');
		next();
	},
	(req: express.Request, res: express.Response) => {
		setTimeout(() => {
			// Disable caching for content files
			console.log(new Date());
			res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
			res.header('Pragma', 'no-cache');
			res.header('Expires', '0');
			res.render('index', { title: 'Express' });
		}, 5000);
	}
);

結果是

  1. node 應用的 mian thread 在第一時間就呼叫了 50 次 API ,
  2. API 也在第一時間收到 50 個 request,
  3. 接著 API 的 main thread 閒置 5 秒 ( TP 當然也是閒置 ) ,
  4. main thread 醒來叫醒 threadpool 幫忙處理要渲染的資料 ( 一個一個處理 )
  5. main thread 一發現有 threadpool 處理好的資料就馬上回傳

所以總共只有停下來等待了 5 秒鐘, 因為 Network IO 會被 main thread 掌管, 其沒有數量限制的瘋狂切換, 以近乎同時的方法處理所有 IO , 而本地資料抓取與渲染, 會被 TP 一個一個處理, 處理完再交給 main thread 回傳。而 TP 的數量是 4 個 thread

這個現象就像我昨天寫的 IOCP httpServer , main thread 瘋狂的接收 Network IO , 和把 NetWork IO 的事件放入 eventQueue, thread pool 一個一個醒來, 各取一個事件處理與回傳。

當然兩件事還是有蠻大的差別, 千萬別覺得他們差不多。(ex: node 的回傳會丟回來給 main thread 傳, node 的事件拆分沒有那麼粗糙, 所以可以做到更細緻的事件切換 ,.......)

小結

實驗就到這裡, 基本上可以利用 epoll 或 IOCP 這類演算法來解釋, 但實際上 node 底層更為複雜, 就讓我們繼續看下面的附註吧

附註

https://stackoverflow.com/questions/63369876/nodejs-what-is-maximum-thread-that-can-run-same-time-as-thread-pool-size-is-fo

懶人包 :

因為這個現象, 有些人在撰寫 micro service 中具有大量 IO 的微服務時, 會試著提高 node 的 thread 數量。但這篇文章說, 網路 IO 都在 main thread 完成, 所以提高 node 的 thread 數量沒啥用。

總結

Node 的非同步不是單純的非同步 IO , 而是所有可以非同步的事件被分成兩大類, 一類是只能在 main thread 內進行, 但 main thread 卻會在自己一條 thread 內快速切換任務完成所有事情, 舉例就是 network IO , 另一種就是會喚醒沉睡的 thread ( in threadpool ) 且要求他們執行 (但其實這類事件不全是 IO , 但為了方便之後還是統一稱為 IO ) , 像是本地檔案讀寫。

明天進度

簡單看看一個用 JS 撰寫的 model 如何調用底層的 C++

明天見 !


上一篇
[Day 17] IOCP 實作
下一篇
[Day 19] Node http request
系列文
從C到JS的同步非同步探索30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言